package c2.cross.business.championrace;

import c2.advanced.business.base.KvData;
import c2.cross.business.championrace.temp.ChampionRaceTempRankRwdData;
import c2.cross.business.championrace.temp.ChampionRaceTempRedPacketData;
import c2.cross.business.championrace.temp.ChampionRaceTempVideoData;
import cate.common.table.d.GDChampionRace;
import cate.common.util.GameResult;
import cate.common.util.TimeTool;
import cate.game.event.championrace.ChampionRaceTimerEvent;
import cate.game.framework.advanced.AdvancedByteAttributes;
import cate.game.framework.advanced.AdvancedLongAttributes;
import cate.game.pvp.championrace.data.ChampionRaceRank;
import cate.game.pvp.championrace.data.*;
import cate.game.pvp.championrace.timer.ChampionRaceTimerTask;
import cate.game.role.RoleSnapshot;
import com.google.common.collect.Lists;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.data.annotation.Transient;

import java.util.*;
import java.util.stream.Collectors;

import static cate.game.event.championrace.ChampionRaceTimerEvent.EventType.*;

@Getter
@Slf4j
public class ChampionRaceKv extends KvData {
    private int season;
    private long openTime;
    private long closeTime;

    private ChampionRaceSpec spec;
    private List<ChampionRaceTimerTask.Setting> timerSettings;

    private ChampionRaceRankData rankData;
    private ChampionRacePsData psData;
    private ChampionRaceRecordData recordData;
    private ChampionRaceBetData betData;
    private AdvancedByteAttributes opAttributes;
    private AdvancedLongAttributes attributes;

    @Transient
    private ChampionRaceTempVideoData videoData;
    @Transient
    private ChampionRaceTempRedPacketData redPacketData;
    @Transient
    private ChampionRaceTempRankRwdData rankRwdData;

    @Override
    public void init(String key) {
        super.init(key);
        if (recordData == null) {
            recordData = new ChampionRaceRecordData();
        }
        recordData.initialize();
        if (psData == null) {
            psData = new ChampionRacePsData();
        }
        psData.initialize();
        if (betData == null) {
            betData = new ChampionRaceBetData();
        }
        betData.initialize();
        if (rankData == null) {
            rankData = new ChampionRaceRankData();
        }
        rankData.initialize();
        buildKVData();
        videoData = new ChampionRaceTempVideoData();
        redPacketData = new ChampionRaceTempRedPacketData();
        rankRwdData = new ChampionRaceTempRankRwdData();
    }

    private void buildKVData() {
        if (CollectionUtils.isEmpty(timerSettings)) {
            long firstOpenTime = TimeTool.getNextSundayZeroTime();
            this.spec = ChampionRaceSpec.of(1, (byte) 0);
            this.season = 1;
            this.openTime = firstOpenTime;
            this.closeTime = firstOpenTime + GDChampionRace.WHOLE_SEASON_TIME;
            this.timerSettings = Lists.newArrayList(
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1), OPEN, ChampionRaceSpec.of(1, (byte) 0)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "11:55:00"), RANK_SETTLE, ChampionRaceSpec.of(1, (byte) 0)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "11:59:50"), INVITATION, ChampionRaceSpec.of(1, (byte) 0)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:00:00"), TRIAL_GROUP, ChampionRaceSpec.of(1, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:00:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 1, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:03:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 1, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:05:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 1, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:05:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 2, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:08:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 2, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:10:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 2, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:10:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 3, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:13:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 3, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:15:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 3, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:15:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 4, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:18:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 4, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:20:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 4, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:20:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 5, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:23:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 5, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:25:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 5, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:25:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 6, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:28:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 6, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:30:00"), TRIAL, ChampionRaceSpec.of(1, (byte) 1, (byte) 0, (byte) 6, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:30:00"), KO_GROUP, ChampionRaceSpec.of(1, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:30:00"), KO, ChampionRaceSpec.of(1, (byte) 2, (byte) 0, (byte) 1, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:33:00"), KO, ChampionRaceSpec.of(1, (byte) 2, (byte) 0, (byte) 1, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:35:00"), KO, ChampionRaceSpec.of(1, (byte) 2, (byte) 0, (byte) 1, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:35:00"), KO, ChampionRaceSpec.of(1, (byte) 2, (byte) 0, (byte) 2, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:38:00"), KO, ChampionRaceSpec.of(1, (byte) 2, (byte) 0, (byte) 2, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:40:00"), KO, ChampionRaceSpec.of(1, (byte) 2, (byte) 0, (byte) 2, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:40:00"), KO, ChampionRaceSpec.of(1, (byte) 2, (byte) 0, (byte) 3, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:43:00"), KO, ChampionRaceSpec.of(1, (byte) 2, (byte) 0, (byte) 3, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:45:00"), KO, ChampionRaceSpec.of(1, (byte) 2, (byte) 0, (byte) 3, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:45:00"), FINAL_GROUP, ChampionRaceSpec.of(1, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:45:00"), FINAL, ChampionRaceSpec.of(1, (byte) 3, (byte) 0, (byte) 1, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:48:00"), FINAL, ChampionRaceSpec.of(1, (byte) 3, (byte) 0, (byte) 1, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:50:00"), FINAL, ChampionRaceSpec.of(1, (byte) 3, (byte) 0, (byte) 1, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:50:00"), FINAL, ChampionRaceSpec.of(1, (byte) 3, (byte) 0, (byte) 2, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:53:00"), FINAL, ChampionRaceSpec.of(1, (byte) 3, (byte) 0, (byte) 2, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:55:00"), FINAL, ChampionRaceSpec.of(1, (byte) 3, (byte) 0, (byte) 2, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:55:00"), FINAL, ChampionRaceSpec.of(1, (byte) 3, (byte) 0, (byte) 3, (byte) 1)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "12:58:00"), FINAL, ChampionRaceSpec.of(1, (byte) 3, (byte) 0, (byte) 3, (byte) 2)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "13:00:00"), FINAL, ChampionRaceSpec.of(1, (byte) 3, (byte) 0, (byte) 3, (byte) 3)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "13:00:00"), RANK_SWITCH, ChampionRaceSpec.of(1, (byte) 4)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 1, "13:00:00"), SHOW, ChampionRaceSpec.of(1, (byte) 5)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 8), REST, ChampionRaceSpec.of(1, (byte) 6)),
                    ChampionRaceTimerTask.Setting.of(TimeTool.calTimeStamp(firstOpenTime, 8), SWITCH, ChampionRaceSpec.of(1, (byte) 7))
            );
            this.timerSettings.forEach(settings -> settings.getSpec().setSeason(season));
        }
        if (Objects.isNull(attributes)) {
            attributes = AdvancedLongAttributes.of(0L);
        }
        if (Objects.isNull(opAttributes)) {
            opAttributes = AdvancedByteAttributes.of((byte) 0);
        }
    }

    public List<ChampionRacePs> getSortedPs(Comparator<? super ChampionRacePs> comparator) {
        return psData.getSortedPs(comparator);
    }

    public List<ChampionRaceRecord> getRecords(ChampionRaceSpec spec) {
        return recordData.getRecords(spec);
    }

    public List<ChampionRaceRecord> getRecords(ChampionRaceSpec from, ChampionRaceSpec to) {
        return recordData.getRecords(from, to);
    }

    public List<ChampionRaceRecord> getRecords(String uid, ChampionRaceSpec from, ChampionRaceSpec to) {
        return recordData.getRecords(uid, from, to);
    }

    public ChampionRacePs findPs(String roleUid) {
        return psData.findPs(roleUid);
    }

    public List<ChampionRaceOpponent> findPs(int srvId) {
        return psData.findPs(srvId);
    }

    public ChampionRaceBetRecord getBetRecord(ChampionRaceSpec spec) {
        return betData.getBetRecord(spec);
    }

    public List<ChampionRaceBetRecord> getAllBetRecord() {
        return betData.getAllBetRecord();
    }

    public void bet(ChampionRaceSpec spec, byte side, long coins) {
        betData.bet(spec, side, coins);
    }

    public void openSeason(ChampionRaceTimerTask.Setting setting) {
        if (attributes.hasAttribute(OPEN.getFlag())
                && setting.getSpec().getSeason() == this.season) {
            this.openTime = setting.getTriggerTimeMs();
            this.closeTime = setting.getTriggerTimeMs() + getCycleTimeMs();
            return;
        }
        this.opAttributes.clear();
        this.attributes.clear();
        this.spec = setting.getSpec();
        this.season = spec.getSeason();
        this.openTime = setting.getTriggerTimeMs();
        this.closeTime = setting.getTriggerTimeMs() + getCycleTimeMs();
        attributes.setAttribute(OPEN.getFlag());
        log.info("切换spec拉，spec={}", spec);
    }

    public void closeSeason(ChampionRaceTimerTask.Setting setting) {
        if (attributes.hasAttribute(CLOSE.getFlag())) {
            return;
        }
        this.spec = setting.getSpec();
        log.info("切换spec拉，spec={}", spec);
        if (this.season <= 1) {
            timerSettings.forEach(s -> {
                s.getSpec().setSeason(setting.getSpec().getSeason() + 1);
                s.setTriggerTimeMs(s.getTriggerTimeMs() + getCycleTimeMs());
            });
        }
        attributes.setAttribute(CLOSE.getFlag());
    }

    public void switchSeason(ChampionRaceTimerTask.Setting setting) {
        if (attributes.hasAttribute(SWITCH.getFlag())) {
            return;
        }
        this.spec = setting.getSpec();
        log.info("切换spec拉，spec={}", spec);
        this.timerSettings.forEach(s -> {
            s.getSpec().setSeason(setting.getSpec().getSeason() + 1);
            s.setTriggerTimeMs(s.getTriggerTimeMs() + getCycleTimeMs());
        });
        clearSeasonData();
        attributes.setAttribute(SWITCH.getFlag());
    }

    private void clearSeasonData() {
        videoData.clear();
        rankRwdData.clear();
        redPacketData.clear();
        psData.clear();
        betData.clear();
        recordData.clear();
        rankData.clear();
    }

    public boolean rankSettle(ChampionRaceTimerTask.Setting setting, List<ChampionRacePs> psList) {
        if (!attributes.hasAttribute(RANK_SETTLE.getFlag())) {
            psData.addPsList(psList);
            this.spec = setting.getSpec();
            log.info("切换spec拉，spec={}", spec);
            attributes.setAttribute(ChampionRaceTimerEvent.EventType.RANK_SETTLE.getFlag());
        }
        return true;
    }

    public boolean jumpRankSettle(ChampionRaceTimerTask.Setting setting) {
        if (!attributes.hasAttribute(RANK_SETTLE.getFlag())) {
            this.spec = setting.getSpec();
            this.attributes.setAttribute(ChampionRaceTimerEvent.EventType.RANK_SETTLE.getFlag());
            this.opAttributes.setAttribute(1);
            log.info("切换spec拉，spec={}", spec);
        }
        return true;
    }

    public boolean changeSeasonSpec(ChampionRaceTimerTask.Setting setting, int flag, boolean opSkip) {
        if (!canChangeSeasonSpec(setting, flag, opSkip)) {
            return false;
        }
        this.spec = setting.getSpec();
        attributes.setAttribute(flag);
        log.info("切换spec拉，spec={}", spec);
        return true;
    }

    public boolean canChangeSeasonSpec(ChampionRaceTimerTask.Setting setting, int flag, boolean opSkip) {
        if (opSkip && opAttributes.hasAttribute(1)) {
            if (attributes.hasAttribute(flag)) {
                return false;
            }
            this.spec = setting.getSpec();
            attributes.setAttribute(flag);
            log.info("因为跳过标识为1，切换spec拉，spec={}", spec);
            return false;
        }
        return !attributes.hasAttribute(flag);
    }

    private long getCycleTimeMs() {
        return GDChampionRace.WHOLE_SEASON_TIME;
    }

    public void addRecordData(ChampionRaceRecord record) {
        recordData.addRecord(record);
    }

    public void addBetRecordData(ChampionRaceBetRecord betRecord) {
        betData.addBetRecord(betRecord);
    }

    public boolean settleRankReward() {
        List<ChampionRacePs> sortedPs = getSortedPs(
                Comparator.comparing(ChampionRacePs::getOpponent, Comparator.comparing(ChampionRaceOpponent::getWinCount, Comparator.reverseOrder()))
                        .thenComparing(ChampionRacePs::getOpponent, Comparator.comparing(ChampionRaceOpponent::getPoints, Comparator.reverseOrder()))
        );
        if (CollectionUtils.isEmpty(sortedPs)) {
            log.info("ps是空的");
            return true;
        }
        List<ChampionRaceRank> rankList = sortedPs.stream()
                .map(ChampionRaceRank::of)
                .collect(Collectors.toList());
        rankData.addRankDatas(rankList);

        List<ChampionRaceRankRwd> rwdList = Lists.newArrayList();
        log.info("开始计算 [ 跨服冠军赛 ] 玩家排名");
        for (int i = 0; i < sortedPs.size(); i++) {
            ChampionRacePs ps = sortedPs.get(i);
            log.info("玩家:{} ID:{} 排名:{} 当前服:{}", ps.getOpponent().getSnapshot().base.name, ps.getUid(),
                    i + 1, ps.getOpponent().getSnapshot().base.srvId);
            ChampionRaceRankRwd rwd = ChampionRaceRankRwd.of(
                    ps.getUid(),
                    season,
                    ps.getOpponent().getSnapshot().base.srvId,
                    i + 1
            );
            rwdList.add(rwd);
        }
        log.info("结束计算 [ 跨服冠军赛 ] 玩家排名");
        rankRwdData.upsert(rwdList);
        return true;
    }

    public void upsertVideo(ChampionRaceSpec spec, ChampionRaceVideo video) {
        videoData.upsert(spec, video);
    }

    public ChampionRaceRank getRankDate(String roleUid) {
        int rank = rankData.getRankIndex(roleUid);
        if (rank > -1) {
            return rankData.getRankData(rank);
        }
        return null;
    }

    public List<ChampionRaceRank> getRankList() {
        return rankData.getRankList();
    }

    public List<ChampionRaceRedPacketRecord> getRedPacket(ChampionRaceSpec spec, RoleSnapshot roleSnapshot) {
        return redPacketData.getRedPacket(spec, roleSnapshot);
    }

    public List<ChampionRaceRedPacketRecord> getRoleRedPacket(ChampionRaceSpec spec, String roleUid) {
        return redPacketData.getRoleRedPacket(spec, roleUid);
    }

    public List<ChampionRaceRankRwd> getRankRwdList(int season, int srvId) {
        return rankRwdData.getRankRwdList(ChampionRaceRankRwd.getSrvIdIndexName(season, srvId));
    }

    public void updatePs(ChampionRacePs ps) {
        psData.updatePs(ps);
    }

    public GameResult<Void> admire(int index, String roleUid) {
        return rankData.admire(index, roleUid);
    }

    public boolean hasRecvdRedPacket(ChampionRaceSpec spec, String roleUid) {
        return redPacketData.hasRecvdRedPacket(spec, roleUid);
    }
}
